﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Apworks.Repositories;
using Apworks;
using Apworks.Application;
using TinyForum.Infrastructure;
using TinyForum.Domain.Model;
using Apworks.Specifications;
using TinyForum.DataObjects;


namespace TinyForum.Application.Implementation
{
    public class AccountApplication : ApplicationServiceBase, IAccountApplication
    {
        #region IAccountApplication Members

        public long Register(AccountDataObject accountDataObject)
        {
            if (string.IsNullOrEmpty(accountDataObject.UserName))
                throw new ArgumentNullException("userName");
            if (string.IsNullOrEmpty(accountDataObject.Password))
                throw new ArgumentNullException("password");
            if (string.IsNullOrEmpty(accountDataObject.DisplayName))
                throw new ArgumentNullException("displayName");
            if (string.IsNullOrEmpty(accountDataObject.Email))
                throw new ArgumentNullException("email");
            if (string.IsNullOrEmpty(accountDataObject.PasswordQuestion))
                throw new ArgumentNullException("passwordQuestion");
            if (string.IsNullOrEmpty(accountDataObject.PasswordAnswer))
                throw new ArgumentNullException("passwordAnswer");

            using (RepositoryContextManager mgr = new RepositoryContextManager())
            {
                Account account = new Account(accountDataObject.UserName,
                    accountDataObject.Password, accountDataObject.DisplayName,
                    accountDataObject.Avatar, accountDataObject.Email,
                    accountDataObject.IsAdmin, accountDataObject.IsOnline,
                    accountDataObject.IsLocked, accountDataObject.IsApproved,
                    accountDataObject.PasswordQuestion, accountDataObject.PasswordAnswer);
                IRepository<Account> repository = mgr.GetRepository<Account>();
                repository.Add(account);
                mgr.Commit();
                return account.ID;
            }
        }

        public bool AccountExists(string userName)
        {
            if (string.IsNullOrEmpty(userName))
                throw new ArgumentNullException("userName");
            using (RepositoryContextManager mgr = new RepositoryContextManager())
            {
                IRepository<Account> repository = mgr.GetRepository<Account>();
                return repository.Exists(Specification<Account>.Eval(u => u.UserName == userName));
            }
        }

        public bool Authenticate(string userName, string password)
        {
            if (string.IsNullOrEmpty(userName))
                throw new ArgumentNullException("userName");
            if (string.IsNullOrEmpty(password))
                throw new ArgumentNullException("password");

            using (RepositoryContextManager mgr = new RepositoryContextManager())
            {
                IRepository<Account> repository = mgr.GetRepository<Account>();
                Account user = repository.Get(Specification<Account>.Eval(u => u.UserName == userName));
                return user.Password == password;
            }
        }

        public void GrantAdminPrivilege(string userName)
        {
            if (string.IsNullOrEmpty(userName))
                throw new ArgumentNullException("userName");
            using (RepositoryContextManager mgr = new RepositoryContextManager())
            {
                IRepository<Account> repository = mgr.GetRepository<Account>();
                Account user = repository.Get(Specification<Account>.Eval(u => u.UserName == userName));
                user.IsAdmin = true;
                repository.Add(user);
                //context.Commit();
            }
        }

        public IEnumerable<AccountDataObject> GetAccountsByUserName(string userName)
        {
            if (string.IsNullOrEmpty(userName))
                throw new ArgumentNullException("userName");
            using (RepositoryContextManager mgr = new RepositoryContextManager())
            {
                IRepository<Account> repository = mgr.GetRepository<Account>();
                IEnumerable<Account> accounts = repository.FindAll(Specification<Account>.Eval(u => u.UserName == userName));
                if (accounts != null)
                    return accounts.Select(ac => DataObjectMapper.MapToDataObject(ac)).ToList();
                else
                    return null;
            }
        }

        public IEnumerable<AccountDataObject> GetAccountsByEmail(string email)
        {
            if (string.IsNullOrEmpty(email))
                throw new ArgumentNullException("email");
            using (RepositoryContextManager mgr = new RepositoryContextManager())
            {
                IRepository<Account> repository = mgr.GetRepository<Account>();
                IEnumerable<Account> accounts = repository.FindAll(Specification<Account>.Eval(u => u.Email == email));
                if (accounts != null)
                    return accounts.Select(ac => DataObjectMapper.MapToDataObject(ac)).ToList();
                else
                    return null;
            }
        }

        public AccountDataObject GetAccount(long id)
        {
            using (RepositoryContextManager mgr = new RepositoryContextManager())
            {
                IRepository<Account> repository = mgr.GetRepository<Account>();
                Account user = repository.Get(Specification<Account>.Eval(u => u.ID == id));
                return DataObjectMapper.MapToDataObject(user);
            }
        }

        public IEnumerable<AccountDataObject> GetAllAccounts()
        {
            using (RepositoryContextManager mgr = new RepositoryContextManager())
            {
                IRepository<Account> repository = mgr.GetRepository<Account>();
                var accounts = repository.FindAll();
                return accounts.Select(ac => DataObjectMapper.MapToDataObject(ac)).ToList();
            }
        }

        public string GetPassword(string userName, string passwordAnswer)
        {
            var accountDataObjects = this.GetAccountsByUserName(userName);
            if (accountDataObjects != null && accountDataObjects.Count() > 0)
            {
                var accountDataObject = accountDataObjects.First();
                if (accountDataObject.PasswordAnswer == passwordAnswer)
                    return accountDataObject.Password;
                return null;
            }
            return null;
        }

        public bool ChangePassword(string userName, string oldPassword, string newPassword)
        {
            using (RepositoryContextManager mgr = new RepositoryContextManager())
            {
                IRepository<Account> accountRepository = mgr.GetRepository<Account>();
                Account account = accountRepository.Get(Specification<Account>.Eval(ac => ac.UserName == userName));
                bool result = account.ChangePassword(oldPassword, newPassword);
                if (result)
                {
                    accountRepository.Add(account);
                    mgr.Commit();
                }
                return result;
            }
        }

        public bool ChangePasswordQuestionAndAnswer(string userName, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            return GetSingle<Account, bool>(ac => ac.UserName == userName,
                (account, context, repository) =>
                {
                    if (account.Password != password)
                        return false;
                    account.ChangePasswordQuestionAndAnswer(newPasswordQuestion, newPasswordAnswer);
                    repository.Add(account);
                    context.Commit();
                    return true;
                });
        }

        public string ResetPassword(string userName, string answer)
        {
            return GetSingle<Account, string>(account => account.UserName == userName,
                (account, context, repository) =>
                {
                    string newPassword = account.ResetPassword(answer);
                    if (!string.IsNullOrEmpty(newPassword))
                    {
                        repository.Add(account);
                        context.Commit();
                        return newPassword;
                    }
                    else
                        return null;
                });
        }

        public void Lock(string userName)
        {
            GetSingle<Account>(account => account.UserName == userName,
                (account, context, repository) =>
                {
                    account.Lock();
                    repository.Add(account);
                    context.Commit();
                });
        }

        public void Unlock(string userName)
        {
            GetSingle<Account>(account => account.UserName == userName,
                (account, context, repository) =>
                {
                    account.Unlock();
                    repository.Add(account);
                    context.Commit();
                });
        }

        public void UpdateInformation(string userName, string displayName, string email)
        {
            if (string.IsNullOrEmpty(userName))
                throw new ArgumentNullException("userName");
            GetSingle<Account>(account => account.UserName == userName,
                (account, context, repository) =>
                {
                    if (!string.IsNullOrEmpty(displayName))
                        account.DisplayName = displayName;
                    if (!string.IsNullOrEmpty(email))
                        account.Email = email;
                    repository.Add(account);
                    context.Commit();
                });
        }
        #endregion
    }
}
